package principal;

import base.GeradorDePopulacaoInicial;
import base.ParDeSolucoes;
import base.Populacao;
import base.Solucao;
import java.io.File;
import java.time.Duration;
import java.time.Instant;
import operadores.crossover.OperadorCrossover;
import operadores.crossover.TipoOperadorCrossover;
import operadores.mutacao.MutacaoDeAleatorios;
import operadores.mutacao.OperadorMutacao;
import operadores.selecao.OperadorSelecao;
import operadores.selecao.SelecaoPorTorneio;

// Representa um algoritmo genético para o Problema do Caixeiro Viajante.
public class AlgGeneticoCaixeiroViajante {

    // Parâmetros (configuráveis na tela de parâmetros do programa)
    private static int tamanhoDaPopulacao = 50;
    private static int taxaDeMutacao = 1;
    private static int tamanhoDoTorneio = 2;
    private static int maxIteracoesSemMelhoria = 1000;
    private static int limiteDeTempo = 60;
    private static TipoOperadorCrossover tipoOpCross = TipoOperadorCrossover.CROSSOVER_CX;

    // Retorna o tamanho escolhido para a população.
    public static int getTamanhoDaPopulacao() {
        return tamanhoDaPopulacao;
    }

    // Retorna a taxa de mutação escolhida.
    public static int getTaxaDeMutacao() {
        return taxaDeMutacao;
    }

    // Retorna o tamanho escolhido para o torneio (para a seleção).
    public static int getTamanhoDoTorneio() {
        return tamanhoDoTorneio;
    }

    // Retorna o máximo de iterações sem melhoria que foi configurado.
    public static int getMaxIteracoesSemMelhoria() {
        return maxIteracoesSemMelhoria;
    }

    // Retorna o limite de tempo escolhido para a execução do algoritmo.
    public static int getLimiteDeTempo() {
        return limiteDeTempo;
    }

    // Retorna o tipo de operador de crossover escolhido.
    public static TipoOperadorCrossover getTipoOpCross() {
        return tipoOpCross;
    }

    // Atualiza o valor dos parâmetros do algoritmo.
    // Chamado pela tela de parâmetros.
    public static void atualizaParametros(int tamanhoDaPopulacao, int taxaDeMutacao,
            int tamanhoDoTorneio, int maxIteracoesSemMelhoria, int limiteDeTempo,
            TipoOperadorCrossover tipoOpCross) {
        AlgGeneticoCaixeiroViajante.tamanhoDaPopulacao = tamanhoDaPopulacao;
        AlgGeneticoCaixeiroViajante.taxaDeMutacao = taxaDeMutacao;
        AlgGeneticoCaixeiroViajante.tamanhoDoTorneio = tamanhoDoTorneio;
        AlgGeneticoCaixeiroViajante.maxIteracoesSemMelhoria = maxIteracoesSemMelhoria;
        AlgGeneticoCaixeiroViajante.limiteDeTempo = limiteDeTempo;
        AlgGeneticoCaixeiroViajante.tipoOpCross = tipoOpCross;
    }

    // Retorna a melhor solução obtida, para o arquivo de vértices informado,
    // após a execução do algoritmo com os parâmetros configurados.
    public Solucao executa(File arquivo) throws Exception {
        Instant inicio = Instant.now();

        GeradorDePopulacaoInicial gdpi = new GeradorDePopulacaoInicial();
        Populacao populacao = gdpi.gera(arquivo, tamanhoDaPopulacao);

        int geracoesSeguidasSemMelhoria = 0;
        int iteracoes = 0;

        while (geracoesSeguidasSemMelhoria <= maxIteracoesSemMelhoria
                && Duration.between(inicio, Instant.now()).toMillis() <= (limiteDeTempo * 1000)) {
            OperadorSelecao os = new SelecaoPorTorneio(tamanhoDoTorneio);
            ParDeSolucoes pais = os.seleciona(populacao);

            OperadorCrossover oc = tipoOpCross.getOperador();
            ParDeSolucoes filhos = oc.aplica(pais);

            OperadorMutacao om = new MutacaoDeAleatorios();
            if (filhos.getSolucao1().sorteadaParaMutacao(taxaDeMutacao)) {
                om.inverte(filhos.getSolucao1());
            }
            if (filhos.getSolucao2().sorteadaParaMutacao(taxaDeMutacao)) {
                om.inverte(filhos.getSolucao2());
            }

            populacao.atualizaParaProximaIteracao(filhos);

            if (populacao.melhorou()) {
                geracoesSeguidasSemMelhoria = 0;
            } else {
                geracoesSeguidasSemMelhoria++;
            }

            iteracoes++;

            if (iteracoes % 100 == 0) {
                System.out.println(iteracoes + " iterações ("
                        + geracoesSeguidasSemMelhoria + " sem melhoria)...");
            }
        }

        System.out.println("Iterações: " + iteracoes);

        return populacao.getMelhorElemento();
    }
}
